//Copyright (C) 2011 Ron Coleman. Contact: ronncoleman@gmail.com
//
//This library is free software; you can redistribute it and/or
//modify it under the terms of the GNU General Public License
//as published by the Free Software Foundation; either
//version 3 of the License, or (at your option) any later version.
//
//This library is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
//General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this library; if not, write to the Free Software
//Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
package bj.actor
import scala.collection.mutable.HashMap
import scala.actors.Actor
import scala.actors.OutputChannel
import bj.hkeeping.NotOk
import bj.hkeeping.Ok
import bj.hkeeping.Reply
import bj.util.Logs

case class ArriveHuman(pid : Int)
case class Stopped

/** This class implements the table static members */
object Table  {
  val MIN_PLAYERS: Int = 1
  val MAX_PLAYERS: Int = 6
  
  var id : Int = 0  
}

/**
 * This class implement table instances.
 * @param minBet Minimum bet for the table.
 */
class Table(var minBet: Double, var maxBet: Double, val dealerName: String) extends Actor with Logs {
  /** Table's id */
  Table.id += 1  
  val tid = Table.id
  def tableID = tid
  
  var winloss = 0.0
  def tableEarnings = winloss
  
  /** Dealer for this table */
  var _dealer = new Dealer(dealerName)
  
  /** Setters and getters for min, max, and dealer */
  def min = minBet
  def min_= (value:Double):Unit = minBet = value
  def max = maxBet
  def max_= (value:Double):Unit = maxBet = value
  def dealer = _dealer
  def dealer_= (value:String):Unit = _dealer.name = value
  

  /** True if this table is involved in a game */
  var trucking: Boolean = false

  /** Bet amounts by player id */
  var bets = HashMap[Int, Double]()

  /** Mailboxes by player id */
  var players = HashMap[Int, OutputChannel[Any]]()
  
  /** House mail box */
  var house : OutputChannel[Any] = null
  
  /** Starts the table */
  start

  /** Gives a string version of the table */
  override def toString : String = "table(" + tid + ", " + minBet + ")"
  
  /** This method receives messages */
  def act {
    loop {
      react {
        // Receives arrival of a (bot) player: mailbox is the player's
        case Arrive(mailbox : OutputChannel[Any], pid : Int, betAmt : Double) =>
          debug(this+" received ARRIVE from "+mailbox+" amt = "+betAmt)
          
          arrive(mailbox,pid,betAmt)
          

        // Receive game over signal from the dealer
        case GameOver(pays : HashMap[Int,Outcome]) =>
          debug(this + " received game over for "+pays.size+" players")
          
          gameOver(pays)
          
        // Receives an ArriveHuman signal from the house indicating that a HumanPlayer has requested to join the table  
        case ArriveHuman(pid : Int) =>
          arriveHuman(sender,pid,this.min)
          
        // Receives an indication that a player has changed their bet amount  
        case UpdateBet(player, pid, amt) =>
          placed(player, Bet(pid, amt))
          
         
        // Receives game start signal from the house
        case Go =>
          debug(this+" received Go for " + players.size + " players")
          
          house = sender
          
          go
          
        // Receive a Stop signal from the House 
        case Stop =>
          debug(this+" received Stop")
          stop
          
        // Receive a Stopped signal from the Dealer as an acknowledgment that the game has been stopped. Not fully implemented  
        case Stopped =>
          debug(this+" was told by the dealer that the game has been stopped")
          

      }
    }
  }
  
  /**
   * Processes a (bot) player arrival
   * @param source Player's mailbox
   * @param pid Player's id
   * @param betAmt Player's bet amount
   */
  def arrive(source : OutputChannel[Any], pid : Int, betAmt : Double) {
    val reply = placed(source, Bet(pid, betAmt))

    debug(this + " bet = " + reply)

    source ! reply    
  }
  
  /**
   * Handles the arrival of a human player at the table
   */
  def arriveHuman(source : OutputChannel[Any], pid : Int, betAmt : Double) {
    val reply = placed(source, Bet(pid, betAmt))

    debug(this + " bet = " + reply)

    source ! reply
    
    dealer ! NewHumanPlayer(pid,5,source)
  }
  
  /** Handles game start */
  def go {
    val bettors = players.foldLeft(List[OutputChannel[Any]]())((xs, x) => xs ::: List(x._2))

      debug(this + " dealing " + bettors.size + " bettors")

      dealer ! MinMaxBets(min,max)
      dealer ! GameStart(bettors)
   
  }
  /** Handles stopping a table. Not fully implemented */
  def stop {
	  dealer ! Stop
  }
  
  /**
   * Places the bet after validation.
   * @param mailbox Player's mailbox
   * @param bet Bet parameters
   */
  def placed(mailbox : OutputChannel[Any], bet : Bet) : Reply = {
    debug("table: placing bet amt = "+bet.amt+" num bets = "+bets.size)
    if(bet.amt <= 0 || bets.size >= Table.MAX_PLAYERS)
      return NotOk
            
    players.get(bet.player) match {
      case None =>
        debug("table: adding new player id = "+bet.player)
        players += bet.player -> mailbox

        bets += bet.player -> bet.amt

      case Some(player) =>
        bets.get(bet.player) match {
          case Some(oldAmt) =>
            debug("table: updating bet for player id = "+bet.player)
            bets(bet.player) = bet.amt

          case None =>
            debug("table: got bad bet")
                
            return NotOk
        }
    }
    
    Ok
  }
  
  /** Handles game over */
  def gameOver(pays : HashMap[Int,Outcome]) = {
    pays.foreach(p => pay(p))
  }
  
  /**
   * Sends payment to player.
   * @param pid Player id
   * @parm outcome Game outcome for player pid
   */
  def pay(figure : (Int,Outcome)) : Unit = {
    val (pid, outcome) = figure
        
    outcome match {
      case Win(gain) =>
        debug("player(" + pid + ") won " + gain)
        players(pid) ! outcome
        winloss -= getBet(pid)*gain

      case Lose(gain) =>
        debug("player(" + pid + ") lost " + gain)
        players(pid) ! outcome
        winloss -= getBet(pid)*gain

      case Push(gain) =>
        debug("player(" + pid + ") push " + gain)
        players(pid) ! outcome
    }
  }
  
  /**
   * Gets a current bet at the table, given the pid
   */
  def getBet(pid: Int) : Double = {
    var bet = 0.0
    bets.get(pid) match {
      case None =>
        debug("could not find player "+pid)
      case Some(amt) =>
        bet = amt
    }
    bet
  }
  
  /** Clears all the bets -- NOT USED */
  def clear: Unit = bets.clear
  
  /** 
   * Creates an array of table attributes to be used in the GUI
   */
  def toArray() : Array[AnyRef] = {
    var array : Array[AnyRef] = new Array(6)
    var playerNum = 0
    array(0) = tid.toString
    array(1) = String.format("%3.2f", double2Double(minBet))
    array(2) = String.format("%3.2f", double2Double(maxBet))
    array(3) = players.foldRight("")((newPlayer,playersList) => playersList + getPlayerName(newPlayer))

    def getPlayerName(thisPlayer : (Int, OutputChannel[Any])) : String = {
      playerNum+=1
      if (playerNum>1) ", " + thisPlayer._2.receiver.asInstanceOf[Player].name
      else thisPlayer._2.receiver.asInstanceOf[Player].name
    }
    array(4) = dealer.name
    array(5) = String.format("%3.2f", double2Double(winloss))
      debug(array(0)+" "+array(1)+" "+array(2)+" "+array(3)+" "+array(4)+" "+array(5))
    array
  }
}